Definición objetivo
Conjunto de datos
Decidir dónde, cuándo y cómo invertir no esta una tarea básica. Existen diversas variables que han de investigarse y tomar en consideración, sobretodo cuando se trata de una decisión que abarca una cantidad de capital significativa.Por ello, cuanta más información de calidad (importante!) se recopile sobre la inversión, más preciso es el resultado.
Nuestra finalidad es construir diferentes modelos de regresión sobre los precios de las propiedades del portal Idealista, inegrando el registro de viviendas turísticas y estancias turísticas en viviendas de la isla de Mallorca como fuente externa.
La evaluación de propiedades se ha resuelto a lo largo de los años desde varias perspecticas. Desde la aplicación del método de precios hedónicos para explicar las variables determinantes, hasta el uso de técnicas complejas como las redes neuronales.
Dado el interés en una área concreta, se cubre la zona geográfica del municipio de Palma de Mallorca dentre de un radio seleccionado.
El portal inmobiliario Idealista proporciona una API para quienes quieran disponer de la información sobre viviendas en su base de datos.A partir de una url creada con unas premisas definidas por Idealista, cualquier usuario puede obtener la información. Para ello, proporcionan las claves de acceso, junto con la documentación en la que informan sobre las restricciones de uso. No se pueden realizar más de 100 llamadas por usuario y mes. Cada llamada devuelve un máximo de 50 observaciones. La búsqueda se ha de realizar por coordenadas geoespaciales.
A partir de las credenciales que nos han facilitado, solicitamos nuestro token. Para ello codificamos Apikey:Secreto en Base64, un sistema de numeración posicional que usa 64 como base y se aplica en la codificación. Recibido el token, solicitamos datos a la API de Idealista mediante los parámetros de búsqueda a partir de una posición (latitud y longitud) y un radio. En nuestro proyecto, se plantean varias llamadas hasta obtener un conjunto elevado de instancias.
El punto referente al número de estancias turísticas lo obtenemos en el catálogo de datos abiertos del Gobierno, publicado por el Gobierno de las Islas Baleares.
Del conjunto de datos de viviendas turísticas en Mallorca nos interesa las viviendas en la localidad de Palma de Mallorca. Todas ellas se encuentran dadas de alta, por lo tanto, tienen actividades en las distintas modalidades vacacionales.
import pandas as pd
import glob
import numpy as np
# campo uso
col_list = ['elementList__propertyCode','elementList__floor','elementList__price',
'elementList__propertyType', 'elementList__size', 'elementList__exterior',
'elementList__rooms', 'elementList__bathrooms', 'elementList__district',
'elementList__neighborhood', 'elementList__latitude',
'elementList__longitude', 'elementList__distance', 'elementList__status',
'elementList__newDevelopment', 'elementList__hasLift',
'elementList__priceByArea', 'elementList__detailedType__typology']
# Concatenacion multiples archivos
df = pd.concat([pd.read_csv(f, usecols=col_list) for f in glob.glob('idea-api/*.csv')],
ignore_index = True)
df.head()
| elementList__propertyCode | elementList__price | elementList__propertyType | elementList__size | elementList__exterior | elementList__rooms | elementList__bathrooms | elementList__district | elementList__neighborhood | elementList__latitude | elementList__longitude | elementList__distance | elementList__status | elementList__newDevelopment | elementList__hasLift | elementList__priceByArea | elementList__detailedType__typology | elementList__floor | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 85623117 | 495000.0 | flat | 107.0 | False | 2 | 2 | Ciutat Antigua | La Seu - Cort - Monti-Sion | 39.569071 | 2.654863 | 397 | good | False | True | 4626.0 | flat | NaN |
| 1 | 37153450 | 650000.0 | chalet | 300.0 | False | 3 | 3 | La Vileta - Son Rapinya | Son Rapinya - Los Almendros | 39.585049 | 2.618004 | 3265 | good | False | NaN | 2167.0 | chalet | NaN |
| 2 | 85468337 | 295000.0 | flat | 60.0 | False | 1 | 1 | Ciutat Antigua | La Llotja | 39.569706 | 2.643830 | 550 | good | False | False | 4917.0 | flat | NaN |
| 3 | 85303105 | 468000.0 | penthouse | 100.0 | False | 3 | 2 | Sta Catalina - Son Armadans - Maritim | El Terreno | 39.560699 | 2.626478 | 2254 | good | False | False | 4680.0 | flat | NaN |
| 4 | 40298434 | 850000.0 | penthouse | 144.0 | False | 3 | 2 | Las Avenidas | Pere Garau | 39.574096 | 2.657899 | 839 | good | False | True | 5903.0 | flat | NaN |
Se adjunta tabla con la descripción de las variables (renombradas)
| Variable | Descripción |
|---|---|
| id_property | Número de identificación de la propiedad |
| floor | Nivel de planta en la que se encuentra la propiedad |
| price | Precio de la propiedad en euros |
| propertyType | Tipo de propiedad |
| size | Tamaño en metros cuadros de la propiedad |
| exterior | Booleano (True o False) sobre la ubicación al exterior |
| rooms | Número de habitaciones del inmueble |
| bathrooms | Número de baños del inmueble |
| district | Distrito en el que se encuentra el inmueble |
| neighborhood | Barrio en el se encuentra el inmueble |
| latitude | Coordenada latitud (UTM) |
| longitude | Coordenada longitud (UTM) |
| distance | Distancia entre el inmueble y el centro del municipio |
| status | Condición de preservación de la propiedad |
| newDevelopment | Booleano (True o False) si la propiedad es de nueva obra. |
| hasLift | Booleano (True o False) si la propiedad tiene ascensor |
| priceByArea | Precio por área de la propiedad |
| typology | Tipo de inmueble |
Comprobamos instancias repetidas
# Revisar duplicidad
duplicate = df[df.duplicated()]
duplicate
| elementList__propertyCode | elementList__price | elementList__propertyType | elementList__size | elementList__exterior | elementList__rooms | elementList__bathrooms | elementList__district | elementList__neighborhood | elementList__latitude | elementList__longitude | elementList__distance | elementList__status | elementList__newDevelopment | elementList__hasLift | elementList__priceByArea | elementList__detailedType__typology | elementList__floor |
|---|
# Renombrar columnas
df.columns = ['id_property', 'price', 'propertyType', 'size',
'exterior', 'rooms', 'bathrooms', 'district',
'neighborhood', 'latitude', 'longitude','distance',
'status', 'newDevelopment', 'hasLift', 'priceByArea', 'typology','floor']
df.head()
| id_property | price | propertyType | size | exterior | rooms | bathrooms | district | neighborhood | latitude | longitude | distance | status | newDevelopment | hasLift | priceByArea | typology | floor | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 85623117 | 495000.0 | flat | 107.0 | False | 2 | 2 | Ciutat Antigua | La Seu - Cort - Monti-Sion | 39.569071 | 2.654863 | 397 | good | False | True | 4626.0 | flat | NaN |
| 1 | 37153450 | 650000.0 | chalet | 300.0 | False | 3 | 3 | La Vileta - Son Rapinya | Son Rapinya - Los Almendros | 39.585049 | 2.618004 | 3265 | good | False | NaN | 2167.0 | chalet | NaN |
| 2 | 85468337 | 295000.0 | flat | 60.0 | False | 1 | 1 | Ciutat Antigua | La Llotja | 39.569706 | 2.643830 | 550 | good | False | False | 4917.0 | flat | NaN |
| 3 | 85303105 | 468000.0 | penthouse | 100.0 | False | 3 | 2 | Sta Catalina - Son Armadans - Maritim | El Terreno | 39.560699 | 2.626478 | 2254 | good | False | False | 4680.0 | flat | NaN |
| 4 | 40298434 | 850000.0 | penthouse | 144.0 | False | 3 | 2 | Las Avenidas | Pere Garau | 39.574096 | 2.657899 | 839 | good | False | True | 5903.0 | flat | NaN |
Se descartan las siguientes variables obtenidas en la llamadas, y se hace mención al motivo por el cual de descarta. Además, la API devuelve variables que contienen información sobre las pagínas de la web del portal (página en la que se encuentra,items..) y variables sobre las propiedades que cuentan con parking.
| Variables descartadas | Motivo |
|---|---|
| elementListthumbnail | Imagen |
| elementListexternalReference | Identificador de referencia |
| elementListnumPhotos | Número de fotos |
| elementListoperation | Todas las operaciones son de venta |
| elementListaddress | Direcciones |
| elementListprovince | Provincias (Illes Balears) |
| elementListmunicipality | Municipio |
| elementListcountry | Símbolo del país (es) |
| elementListshowAddress | Booleano sobre si los propietarios quieren mostrar la dirección |
| elementListurl | Dirección web del inmueble |
| elementListhasVideo | Booleano sobre si incluye video |
| elementListsuggestedTextssubtitle | Sugerencias del portal de subtítulo |
| elementListsuggestedTextstitle | Sugerencias del portal de título |
| elementListhasPlan | Booleano sobre si incluye plano |
| elementListhas3DTour | Booleano si inlcluye tour tridimensional |
| elementListhas360 B | ooleano si incluye vista panorámica |
| elementListhasStaging | |
| elementListtopNewDevelopment | Booleano si se encuentra en el top ranking de nuevas obras |
En el caso de la cantidad de ceros en las columnas rooms y bathrooms en un escenario real, toda vivienda cuenta con al menos una habitación y baño se el tipo de propiedad habitable que sea.
La cantidad de valores únicos nos muestra que no hay redundancia en las propiedades (1000 propiedades distintas,1000 observaciones). Disponemos de 6 tipos de propiedades (propertyType). Para las variables de tipo booleano, hemos de revisar hasLift, puesto que nos indica 3 valores distintos. Del mismo modo para status, existe un cuarto estado que no estaba propuesto, y se ha de estudiar.
En cuanto a los distritos y barrios, no se repiten por errores de redacción o similares, aunque en el distrito Portixol-Molinar, no se indica barrio, porque se refiere a la misma zona con el mismo nombre.
from dataprep.eda import plot, plot_missing
plot_missing(df)
NumExpr defaulting to 4 threads.
| Missing Cells | 471 |
|---|---|
| Missing Cells (%) | 2.6% |
| Missing Columns | 4 |
| Missing Rows | 338 |
| Avg Missing Cells per Column | 26.17 |
| Avg Missing Cells per Row | 0.47 |
variable propertyType
# Para la variable ‘propertyType’ se tiene 6 niveles
# Conversion a variable categorica 'propertyType'
df['propertyType'] = df['propertyType'].astype('category')
df.propertyType.unique()
['flat', 'chalet', 'penthouse', 'duplex', 'countryHouse', 'studio'] Categories (6, object): ['flat', 'chalet', 'penthouse', 'duplex', 'countryHouse', 'studio']
variable neighborhood
# En el caso de barrios, el nombre del barrio del distrito Portixol-Molinar toma el mismo nombre.
# En el conjunto de datos, el portal Idealista por defecto no se especifica, por lo que hemos considerado
# en dotarle del mismo nombre.
df = df.replace(np.nan, ' ', regex=True)
df.loc[(df.neighborhood == " "), "neighborhood"] = 'Portixol-Molinar'
# Conversion a variable categorica ‘neighborhood’
df["neighborhood"] = df["neighborhood"].astype('category')
list(df.neighborhood.unique())
['La Seu - Cort - Monti-Sion', 'Son Rapinya - Los Almendros', 'La Llotja', 'El Terreno', 'Pere Garau', 'Son Armadams', 'La Bonanova - Porto Pi', 'Sant Jaume', 'Portixol-Molinar', 'La Missio - Mercat', "Camp d'En Serralta ", 'Sta Catalina - El Jonquet', 'Paseo Marítimo', 'Son Dameto', 'Bons Aires', 'Son Espanyolet', 'Son Serra - La Vileta - Son Pereto', 'Hostalets - Son Fortesa Sud', 'Son Dureta', 'Camp Redo', 'La Soledat', 'Son Cotoner', 'Foners', 'Sindicat', 'Son Cladera - Son Fuster', 'Rafal Nou - Estadi Balear - Son Malferit', 'Son Gotleu', 'El Forti', 'Marqués de la Fontsanta', 'Plaça de Toros', 'Nou Llevant', 'La Teulera', 'Cala Major', 'Zona Colegios - Son Moix', "L'Olivera - Amanecer", 'Son Canals - Can Capes', 'Cas Capiscol', 'Rafal Vell', 'El Vivero', 'Genova', 'Arxiduc', 'Son Oliva', 'Son Xigala', "Coll d'En Rabassa"]
variable status
# El cuarto nivel de la variable ‘status’ se puede tratar de un error,
# o simplemente no ha sido registrado. Dado que solo se trata de dos observaciones,
# procedemos a reemplazarlo por ‘good’ y no descartarla.
# Observacion con cuarto nivel vacio
df.loc[df.status == ' ']
# Reemplazar nivel vacio por valor 'good'
df.loc[(df.status == " "), "status"] = 'good'
# Conversion a variable categorica 'status'
df['status'] = df['status'].astype('category')
#Niveles de variable satutus
df.status.unique()
['good', 'renew', 'newdevelopment'] Categories (3, object): ['good', 'renew', 'newdevelopment']
variable hasLift
Para la variable hasLift existe un tercer nivel que no es booleano. Para tratar de conocer a qué tipo de propiedades pertenece, puesto que en ocasiones no se indica porque se presupone que no procede y que se entiende el motivo por el que no tiene. Este tipos de propiedades son chalet y casas de campo en nuestro caso. Ambos suman 114 y 5 observaciones respectivamente. Para reducir el número de valores del tercer nivel vacío, indicaremos que los dos tipos de propiedades no tienen ascensor (False).
Para el resto de tipo de propiedades, pueden tener o no ascensor, y en estos casos se desconoce. El tratamiento a los casos que desconocemos, es convertir el valor en un string ‘unknow’
# Niveles variable 'hasLift'
df.hasLift.unique()
# Tipo de propiedades con hasLift = " "
df.loc[df.hasLift == ' '].groupby('propertyType')['hasLift'].count()
propertyType chalet 114 countryHouse 5 duplex 1 flat 22 penthouse 3 studio 0 Name: hasLift, dtype: int64
# Reemplazar nivel en hasLift vacio de propiedad chalet por False
df.loc[(df["propertyType"] == "chalet") & (df["hasLift"] == ' '), 'hasLift'] = 'False'
# Reemplazar nivel en hasLift vacio de propiedad countryHouse por False
df.loc[(df["propertyType"] == "countryHouse") & (df["hasLift"] == ' '), 'hasLift'] = 'False'
# Reemplazar nivel en hasLift vacio " " mediante string 'unknown'
df.loc[(df.hasLift == ' ', "hasLift")] = "Unknown"
# Conversion a variable categorica 'hasLift'
# Previamente la convertimos a variable str para que no tenga en cuenta valores booleanos
df['hasLift'] = df['hasLift'].astype(str)
df['hasLift'] = df['hasLift'].astype('category')
df.hasLift.unique()
['True', 'False', 'Unknown'] Categories (3, object): ['True', 'False', 'Unknown']
variable floor
En el caso de la variable ‘floor’ tratamos de armonizar los niveles, y establecer los niveles numéricamente.
Los siguientes casos se agruparán bajo el mismo nivel (nivel 1) :
# Niveles de variable floor
df.floor.unique()
array([' ', '1', '2', 'bj', '4', '5', '6', '3', '16', '7', '12', '9', '8',
'ss', 'en', '10', '11'], dtype=object)
# Total de tipo de propiedades con floor == 'bj'
df.loc[df.floor == 'bj'].groupby('propertyType')['floor'].count()
propertyType chalet 12 countryHouse 1 duplex 7 flat 60 penthouse 1 studio 1 Name: floor, dtype: int64
# Total de tipo de propiedades con floor == 'en'
df.loc[df.floor == 'en'].groupby('propertyType')['floor'].count()
propertyType chalet 0 countryHouse 0 duplex 0 flat 2 penthouse 0 studio 0 Name: floor, dtype: int64
# Total de tipo de propiedades con floor == 'ss'
df.loc[df.floor == 'ss'].groupby('propertyType')['floor'].count()
propertyType chalet 0 countryHouse 0 duplex 0 flat 0 penthouse 0 studio 1 Name: floor, dtype: int64
# Total de tipo de propiedades con floor == ' '
df.loc[df.floor == ' '].groupby('propertyType')['floor'].count()
propertyType chalet 100 countryHouse 4 duplex 15 flat 119 penthouse 42 studio 0 Name: floor, dtype: int64
# Reemplazar nivel en floor 'bj', 'en' y 'ss' de todas las propiedades por nivel 1
df.loc[ (df["floor"] == " ") | (df["floor"] == "bj") | (df["floor"] == "en") |
(df["floor"] == "ss"), "floor"] = '1'
# Reemplazar nivel en floor '' de las propiedades 'chalet' y 'countryHouse' por nivel 1
df.loc[ ((df["propertyType"] == "chalet") | (df["propertyType"] == "countryHouse") )&
(df["floor"] == " "), "floor"] = '1'
df.floor.unique()
array(['1', '2', '4', '5', '6', '3', '16', '7', '12', '9', '8', '10',
'11'], dtype=object)
Variables listas
Las siguientes variables se encuentran preparadas, a falta de su conversión como variables categóricas:
# Conversion a variable categorica de 'exterior'
df['exterior'] = df['exterior'].astype('category')
df.exterior.unique()
[False, True] Categories (2, object): [False, True]
# Conversion a variable categorica de 'district'
df['district'] = df['district'].astype('category')
list(df.district.unique())
['Ciutat Antigua', 'La Vileta - Son Rapinya', 'Sta Catalina - Son Armadans - Maritim', 'Las Avenidas', 'Genova - Bonanova - Sant Agustí', 'Portixol-Molinar', 'Es Forti - Son Cotoner - Son Dameto', 'Llevant - La Soledat', 'Son Oliva - Plaza Toros - Camp Redó', 'Rafal - Son Forteza', 'Secar de la Real', 'Playa de Palma']
# Conversion a variable categorica de 'newDevelopment'
df['newDevelopment'] = df['newDevelopment'].astype('category')
df.newDevelopment.unique()
[False, True] Categories (2, object): [False, True]
# Conversion a variable categorica de 'typology'
df['typology'] = df['typology'].astype('category')
df.typology.unique()
['flat', 'chalet', 'countryHouse'] Categories (3, object): ['flat', 'chalet', 'countryHouse']
variables categóricas
Para las variables categóricas se muestra los resultados ordendados según la frecuencia, que rápidamente analiza las categorías con mayor frecuencia y qué porcentajes representan.
De la lectura general por medio de las gráficas, destacamos lo siguiente:
variables numéricas
El precio medio de las propiedades es de 661414 euros. Detectamos valores atípicos que se alejan del precio medio (pueden deberse a propiedades como chalets) d por ejemplo 5,9 millones de euros.
El tamaño medio de las propiedades es de 155.8 metros cuadrados.Al mismo tiempo observamos propiedades de hasta 1050 metros cuadrados. Seguramente cuenten el terreno que pertenece a la propiedad.
La media de habitaciones es de 3. El 80 % de propiedades tienen un máximo de 4 habitaciones, aunque vemos propiedades con incluso 12 habitaciones. Estos datos bien puede pertencer a un caso real o a un error de registro.
El 50 % de propiedades tienen más de 6 viviendas turísticas cerca a menos de 500 metros. Esto indicador puede tener mayor incidencia sobre el precio de las propiedades.
plot(df)
| Number of Variables | 18 |
|---|---|
| Number of Rows | 1000 |
| Missing Cells | 0 |
| Missing Cells (%) | 0.0% |
| Duplicate Rows | 0 |
| Duplicate Rows (%) | 0.0% |
| Total Size in Memory | 150.3 KB |
| Average Row Size in Memory | 153.9 B |
| Variable Types |
|
| id_property is skewed | Skewed |
|---|---|
| price is skewed | Skewed |
| size is skewed | Skewed |
| rooms is skewed | Skewed |
| bathrooms is skewed | Skewed |
| priceByArea is skewed | Skewed |